﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using iFinity.DNN.Modules.UrlMaster;
using iFinity.DNN.Modules.CatalookUrlProvider.Entities;
namespace iFinity.DNN.Modules.CatalookUrlProvider 
{
    public class CatalookModuleProvider : iFinity.DNN.Modules.UrlMaster.ModuleFriendlyUrlProvider
    {
        #region private members
        FriendlyUrlOptions _options = null; //local copy of the Friendly Url Options, which control things like the inclusion of hyphens instead of spaces, etc.
        //the following set of members are specific to this module, and are used to control how the module provider works.
        //these are read from the web.config file, and appear as attributes in the Module Provider declaration
        protected int _noDnnPagePathTabId = -1; //- used to specify if the  module is on a page where the page path should not be included in the url.
        protected string _urlPath;//- specifies the url path to use for a Catalook Url

        protected int _startingProductId;
        protected string _productUrlSource;
        protected string _categoryUrlSource;
        protected string _advCatUrlSource;

        protected bool _hideListParm;
        protected int _listParmToHide;
        protected bool _isValid;
        protected bool _hideCategoryForProducts;
        protected bool _productIdInUrl;
        #endregion

        #region overridden methods and properties
        /// <summary>
        /// Constructor for the Catalook Url Provider.  This is called by the Url Master module when loading the Provider
        /// </summary>
        /// <param name="name">Name is supplied from the web.config file, and specifies the unique name of the provider</param>
        /// <param name="attributes">Attributes are the xml attributes from the file</param>
        /// <param name="portalId">The portalId is supplied for the calling portal.  Each instance of the provider is portal specific.</param>
        public CatalookModuleProvider(string name, NameValueCollection attributes, int portalId)
            : base(name, attributes, portalId)
        {
            //look for an attribute specifying which tab the module
            //will not use a page path for.  There can only be one
            //tab specified per portal (because there is no page path, then it
            //can only be a single page, as the way of determining one dnn
            //page from another isn't in the reuqested Url)
            string noDnnPagePathTabRaw = attributes["noDnnPagePathTabId"];
            int.TryParse(noDnnPagePathTabRaw, out _noDnnPagePathTabId);

            _urlPath = attributes["urlPath"];

            //catalook specific options
            string startingProductIdRaw = attributes["startingProductId"];
            int.TryParse(startingProductIdRaw, out _startingProductId);
            _productUrlSource = attributes["productUrlSource"];
            _categoryUrlSource = attributes["categoryUrlSource"];
            _advCatUrlSource = attributes["advCatUrlSource"];
            string raw = attributes["hideListParm"];
            if (string.IsNullOrEmpty(raw))
                _hideListParm = true;//default value
            else
                bool.TryParse(raw, out _hideListParm);
            
            raw = attributes["listParmToHide"];
            if (string.IsNullOrEmpty(raw))
                _listParmToHide = 0;
            else
                int.TryParse(raw, out _listParmToHide);

            raw = attributes["hideCategoryForProducts"];
            if (string.IsNullOrEmpty(raw))
                _hideCategoryForProducts = false;
            else
                bool.TryParse(raw, out _hideCategoryForProducts);

            raw = attributes["productIdInUrl"];
            if (string.IsNullOrEmpty(raw))
                _productIdInUrl = false;
            else
                bool.TryParse(raw, out _productIdInUrl);
            
        }
        /// <summary>
        /// The Change Friendly Url method is called for every Url generated when a page is generated by DotNetNuke.  This call sits 'underneath' the 'NavigateUrl' call in DotNetNuke.
        /// Whenever your module calls NavigateUrl, this method will be also called.  In here, the module developer should modify the friendlyUrlPath to the final state required.
        /// However, because this call is used for all Urls on the page, not just those generated by the target module, some type of high-level filter should be used to make sure only
        /// the module Urls are modified.
        /// 
        /// </summary>
        /// <param name="tab">Current Tab</param>
        /// <param name="friendlyUrlPath">Current Friendly Url Path after going through the Friendly Url Generation process of the Url Master module.</param>
        /// <param name="options">The options currently applying to Urls in this portal (space replacement, max length, etc)</param>
        /// <param name="cultureCode">The culture code being used for this Url (if supplied, may be empty)</param>
        /// <param name="endingPageName">The page name the Url has been called with. Normally default.aspx, but may be different for some modules.</param>
        /// <param name="useDnnPagePath">Out parameter to be set by the module.  If true, the path of the DNN page will be in the Url (ie /pagename).  If false, this part of the Url will be removed. </param>
        /// <param name="messages">List of debug messages.  Add any debug information to this collection to help debug your provider.  This can be seen in the repsonse headers, and also in the 'test Url Rewrite' page in the Url Master module.</param>
        /// <returns></returns>
        public override string ChangeFriendlyUrl(DotNetNuke.Entities.Tabs.TabInfo tab, string friendlyUrlPath, iFinity.DNN.Modules.UrlMaster.FriendlyUrlOptions options, string cultureCode, ref string endingPageName, out bool useDnnPagePath, ref List<string> messages)
        {
            _options = options;//keep local copy of options
            //set default values for out parameters
            useDnnPagePath = true;
            if (messages == null) messages = new List<string>();
            //check if we want to try and modify this Url
            //first check to see if this Url is an 'edit' Url - something that loads a module-specific page.
            //we don't want to mess with these, because they're always permissions based Urls and thus
            //no need to be friendly
            if (friendlyUrlPath.Length > 0 && Regex.IsMatch(friendlyUrlPath, @"(^|/)(mid|moduleId)/\d+/?", RegexOptions.IgnoreCase) == false)
            {
                CatalookUrlOptions clOptions = GetCatalookOptions(tab.PortalID);
                CategoryTabTable categoryTabs = UrlController.GetCategoryTabIndex(tab.PortalID, this, clOptions, false);
                bool replacementFound = false;
                bool productFound = true;
                if (!replacementFound)
                {
                    //try and match incoming friendly url path to product Id
                    Regex productUrlRegex = new Regex(@"(?<l>/)?ProductId/(?<productId>\d+)", RegexOptions.IgnoreCase);
                    Match productUrlMatch = productUrlRegex.Match(friendlyUrlPath);
                    if (productUrlMatch.Success)
                    {
                        string productUrl = "";
                        replacementFound = UrlController.MakeProductUrl(this, productUrlMatch, productUrlRegex, friendlyUrlPath, tab, options, clOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out productUrl);
                        if (replacementFound)
                            friendlyUrlPath = productUrl;
                    }
                    productFound = replacementFound;
                }
                if (!replacementFound || productFound) //check product again, might be advanced category / product Url
                {
                    //try and match incoming friendly url path to product Id
                    //Regex advCatUrlRegex = new Regex(@"(?<l>/)?CategoryId/(?<advCatId>\d+)(?<list>/List/\d)?/Level/a", RegexOptions.IgnoreCase);
                    Regex advCatUrlRegex = new Regex(@"(?<l>/)?(CategoryId/(?<advCatId>\d+))(?<list>/List/\d)?(?<cpi>/catPageIndex/\d+)?/Level/a", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
                    if (advCatUrlRegex.IsMatch(friendlyUrlPath))
                    {
                        if (productFound && _hideCategoryForProducts)
                        {
                            //replace the category information
                            friendlyUrlPath = advCatUrlRegex.Replace(friendlyUrlPath, "");
                        }
                        else
                        {
                            MatchCollection advCatUrlMatch = advCatUrlRegex.Matches(friendlyUrlPath);
                            string advCatUrl = "";
                            replacementFound = UrlController.MakeAdvCatUrl(this, advCatUrlMatch, advCatUrlRegex, friendlyUrlPath, tab, options, clOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out advCatUrl, categoryTabs);
                            if (replacementFound)
                            {
                                friendlyUrlPath = advCatUrl;
                            }
                        }
                    }
                }

                if (!replacementFound || (productFound && !_hideCategoryForProducts)) //check product again, might be category / product Url
                {
                    //try and match incoming friendly url path to product Id
                    Regex categoryUrlRegex = new Regex(@"(?<l>/)?Category(\d+)?Id/(?<categoryId>\d+)(/Level/(?<lvl>\d+))?", RegexOptions.IgnoreCase);
                    if (categoryUrlRegex.IsMatch(friendlyUrlPath))
                    {
                        MatchCollection categoryUrlMatch = categoryUrlRegex.Matches(friendlyUrlPath);
                        string categoryUrl = "";
                        replacementFound = UrlController.MakeCategoryUrl(this, categoryUrlMatch, categoryUrlRegex, friendlyUrlPath, tab, options, clOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out categoryUrl);
                        if (replacementFound)
                            friendlyUrlPath = categoryUrl;
                    }
                }
                //take off list parm if found
                if (replacementFound && clOptions.HideListParm)
                {
                    string listItemPath = "/List/" + clOptions.ListParmToHide.ToString();
                    //remove the list part from the optionss
                    friendlyUrlPath = Regex.Replace(friendlyUrlPath, listItemPath, "", RegexOptions.IgnoreCase);
                    if (friendlyUrlPath == "") friendlyUrlPath = " ";
                }
            }
            return friendlyUrlPath;
        }
        private CatalookUrlOptions GetCatalookOptions(int portalId)
        {
 	        //get the options from the stored attributes
            string primaryCultureCode = DotNetNuke.Common.Globals.GetPortalSettings().DefaultLanguage;
            CatalookUrlOptions clOptions = new CatalookUrlOptions(_noDnnPagePathTabId, _startingProductId, _productUrlSource, _categoryUrlSource, _advCatUrlSource, primaryCultureCode, _hideListParm, _listParmToHide, _productIdInUrl);
            return clOptions;

        }
        internal new string EnsureLeadingChar(string leading,string path)
        {
            return base.EnsureLeadingChar(leading, path);
        }
        /// <summary>
        /// This method is used by the Url Master Url Rewriting process.  The purpose of this method is to take the supplied array of Url parameters, and transform them into a module-specific querystring for the underlying re-written Url.
        /// </summary>
        /// <param name="urlParms">The array of parameters found after the DNN page path has been identified.  No key/valeu pairs are identified, the parameters are converted from the /key/value/key2/value2 format into [key,value,key2,value2] format.</param>
        /// <param name="tabId">TabId of identified DNN page. </param>
        /// <param name="portalId">PortalId of identified DNN portal.</param>
        /// <param name="options">The current Friendly Url options being used by the module.</param>
        /// <param name="cultureCode">Identified language/culture code, if supplied.</param>
        /// <param name="portalAlias">Identified portalAlias object for the request.</param>
        /// <param name="messages">List of debug messages.  Add to this list to help debug your module.  Can be viewed in the reponse headers of the request, or in the 'Test Url Rewriting' section of the Url Master module.</param>
        /// <param name="status">Out parameter, returns the Http status of the request.  May be 200,301,302, or 404.  For normal rewriting, return a 200 value.</param>
        /// <param name="location">If a 301 or 302 is returned in the status parameter, then this must contain a valid redirect location.  This should be a fully-qualified Url.</param>
        /// <returns>The querystring to be used for rewriting the Url. NOTE: doesn't need to include the tabid if the tabid parameter is > -1</returns>
        public override string TransformFriendlyUrlToQueryString(string[] urlParms, int tabId, int portalId, iFinity.DNN.Modules.UrlMaster.FriendlyUrlOptions options, string cultureCode, DotNetNuke.Entities.Portals.PortalAliasInfo portalAlias, ref List<string> messages, out int status, out string location)
        {
            //store local options variable
            _options = options;
            //initialise results and output variables
            string result = ""; status = 200; //OK 
            location = null; //no redirect location
            if (messages == null) messages = new List<string>();

            Hashtable queryStringIndex = null;
            string path = string.Join("/", urlParms);
            int skipUpToIndex = -1;
            bool found = false;
            bool catMatched = false;
            bool advCatMatched = false;
            bool catInUrl = false;
            string unmatchedHierarchy = null;
            CatalookUrlOptions clOptions = null;
            string redirectUrl = null; bool wrongUrlRedirect = false;
            //look for match on pattern for date and title - the pattern used by this provider
            //string path = string.Join("/", urlParms);
            //messages.Add("Checking for Items in Friendly Url path: " + path);
            if (urlParms.Length > 0)
            {
                clOptions = GetCatalookOptions(portalId);
                //tabid == -1 when no dnn page path is in the Url.  This means the Url Master module can't determine the DNN page based on the Url.
                //In this case, it is up to this provider to identify the correct tabId that matches the Url.  Failure to do so will result in the incorrect tab being loaded when the page is rendered. 
                if (tabId == -1)
                {
                    if (_noDnnPagePathTabId > -1)
                        //tabid -1 means a 'site root' match - meaning that the dnn page path wasn't included in the Url
                        tabId = _noDnnPagePathTabId;//if tabid = -1, it means a site root match (no dnn page path) so we substitute in the tabid where this is being used
                }
                queryStringIndex = UrlController.GetQueryStringIndex(portalId, this, options, clOptions, false);
                int i = 0;
                int level = 0;
                //list is for keeping track of prior qs values added to result, in case of 
                //hierarchial matches
                List<string> hierarchyUrlQsValues = new List<string>();
                string shortHierarchyPath = null; bool listParm = false;
                string longHierarchyPath = null;
                
                foreach (string urlParm in urlParms)
                {
                    if (!catInUrl && urlParm.ToLower() == "categoryid")
                        catInUrl = true;
                    if (_urlPath != null && _urlPath.ToLower() == urlParm.ToLower())
                    {
                        //not this one, will be the next one

                    }
                    else
                    {
                        //check for existence of this value in the querystring index
                        string qsKey = urlParm.ToLower();
                        string qs = (string)queryStringIndex[qsKey];
                        bool matchedSingleParm = false;
                        if (qs != null)
                        {
                            //found a querystring match
                            found = true;
                            hierarchyUrlQsValues.Add(qs);
                            Match levelMatch = Regex.Match(qs, @"&level=(?<lvl>\d+)", RegexOptions.IgnoreCase);
                            if (levelMatch.Success)
                            {
                                level++;//increases for each level-specified url path segment found (ie /cat-1 = level1 /cat-1/cat-2 = level 2
                                //remove the level=x from the qs
                                //qs = qs.Replace(levelMatch.Value, "");
                                if (level > 1)
                                {
                                    string keyName = "Category" + level.ToString() + "ID";
                                    //update the categoryId as well
                                    qs = Regex.Replace(qs, @"categoryId", keyName, RegexOptions.IgnoreCase);
                                }
                            }
                            result = UrlController.CombineQueryStrings(result, qs, true);
                            //result += qs;
                            skipUpToIndex = i;
                            matchedSingleParm = true;
                            //check if this was an adv cat match - prevents trying to match by tab
                            if (!advCatMatched && Regex.IsMatch(qs, @"CategoryId=\d+&level=a", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
                                advCatMatched = true;
                        }
                        else
                        {
                            //look for matching product id on the end
                            if (clOptions.ProductIdInUrl)
                            {
                                Regex prodId = new Regex(@".+(?<cc1>[a-z]{2})(?<cc2>[a-z]{2})(?<prodid>\d+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
                                Match prodIdMatch = prodId.Match(qsKey);
                                if (prodIdMatch.Success)
                                {
                                    //might be a product match
                                    string productId = prodIdMatch.Groups["prodid"].Value;
                                    string ccForKey = prodIdMatch.Groups["cc1"].Value + "-" + prodIdMatch.Groups["cc2"].Value.ToUpper();
                                    string furlkey = "p" + productId.ToString() + "-" + ccForKey;
                                    Hashtable friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(portalId, this, options, clOptions);
                                    if (friendlyUrlIndex.ContainsKey(furlkey))
                                    {
                                        //likely to be a changed Url that still matches on culture/product
                                        //string furl = (string)friendlyUrlIndex[furlkey];
                                        //insert into urls
                                        //string[] redirectParms = new string[urlParms.Length];
                                        List<string> parmList = new List<string>();
                                        int j = 0; 
                                        bool odd = true;
                                        foreach (string val in urlParms)
                                        {
                                            if (j == i) //when current value, replace with key/val
                                            {
                                                parmList.Add("ProductId=" + productId.ToString());
                                            }
                                            else
                                                if (odd)
                                                    parmList.Add(val);
                                                else
                                                    parmList.Add("=" + val);

                                            j++;
                                            odd = !odd;
                                        }
                                        
                                        wrongUrlRedirect = true;
                                        redirectUrl = UrlController.BuildFullyQualifiedFriendlyUrl(portalAlias.HTTPAlias, tabId, parmList.ToArray(), this, ccForKey);
                                    }
                                }
                            }
                        }

                        //now check for matches based on pairs/triples/ect on a hierarchy
                        if (listParm == false && urlParm.ToLower() == "list")
                            listParm = true;
                        //igore the List/0 or List/1 in the Url, if it's there
                        if (longHierarchyPath == null)
                            longHierarchyPath = urlParm;
                        else
                            longHierarchyPath += "/" + urlParm;

                        if (!listParm && !matchedSingleParm)
                        {
                            if (shortHierarchyPath == null)
                                shortHierarchyPath = urlParm;
                            else
                            {
                                shortHierarchyPath += "/" + urlParm;
                                //look up on hierarchy path
                                //928 : use two hierarchy paths : one starting from the begiining,
                                //and one excluding a matched category
                                qs = (string)queryStringIndex[shortHierarchyPath.ToLower()];
                            }
                            if (qs == null && longHierarchyPath != null)
                                qs = (string)queryStringIndex[longHierarchyPath.ToLower()];
                            if (qs != null)
                            {
                                //matched on hierarchial path.
                                //hierarchy matches override individual path-level matches
                                //first remove all prior matches
                                foreach (string qsValue in hierarchyUrlQsValues)
                                {
                                    string qsTrimmed = qsValue.Substring(1);//remove leftmost character
                                    result = result.Replace(qsTrimmed, "");//remove from querystring
                                }
                                //then add on this match
                                found = true;
                                result = UrlController.CombineQueryStrings(result, qs, true);
                                skipUpToIndex = i;
                                //and keep this one, because it might be overridden by a 'better' match.
                                hierarchyUrlQsValues.Add(qs);
                                if (!advCatMatched && qsKey.StartsWith("ac"))
                                    advCatMatched = true;
                                if (!catMatched && qsKey.StartsWith("c"))
                                    catMatched = true;
                            }
                            else
                            {
                                unmatchedHierarchy = shortHierarchyPath;
                            }
                        }
                        else
                            //reset once the /list/n have been passed by
                            if (urlParm.ToLower() != "list")
                                listParm = false;
                    }
                    i++;
                }
                if (level > 0)
                    result += "&Level=" + level.ToString();//put back the highest level found
            }
            if (wrongUrlRedirect)
            {
                //the url was matched, but it was wrong, so issue a redirect
                status = 301;
                location = redirectUrl;
            }
            else
            {
                //check for tab based category rewrite, but only if not already matched, and no category in the Url
                //always checking 
                //and only try and match if the URL is empty - otherwise it should be some type of other match 
                //(ie, don't show default category if there is a url path of some type)
                if (advCatMatched == false && catInUrl == false && urlParms.Length == 0)
                {
                    //check for tab/cat match
                    if (clOptions == null) clOptions = GetCatalookOptions(portalId);
                    TabCategoryTable tabCats = UrlController.GetTabCategoryIndex(portalId, this, clOptions, false);
                    //check to see if this tab has a category assigned
                    if (tabCats != null && tabCats.ContainsKey(tabId))
                    {

                        found = true;
                        int assignedCatId = (int)tabCats[tabId];
                        //ok, now we're going to add this as a querystring
                        if (result.Contains("?"))
                            result += "&CategoryID=" + assignedCatId.ToString() + "&level=a"; //advanced category
                        else
                            result += "?CategoryID=" + assignedCatId.ToString() + "&level=a";//advanced category

                        messages.Add("Category Assigned to tab found in Friendly Url Provider.  Assigned Category Id  : " + assignedCatId);
                    }
                }

                if (found == true)
                {
                    //add on list parm always, if it is specified.
                    if (clOptions.HideListParm)
                        result += "&List=" + clOptions.ListParmToHide.ToString();

                    if (urlParms.Length > 0)
                        messages.Add("Item Matched in Friendly Url Provider.  Url : " + string.Join("/", urlParms) + "; Matched Path : " + path);
                    else
                        messages.Add("Item Matched in Friendly Url Provider.  Url matched on tab Path");

                    //put on any remainder of the path that wasn't to do with the friendly Url
                    string remainder = "";
                    if (unmatchedHierarchy != null)
                    {
                        string[] prefixParts = unmatchedHierarchy.Split('/');
                        string prefixedQs = base.CreateQueryStringFromParameters(prefixParts, -1);//get as querystring
                        remainder += prefixedQs;
                    }
                    //double check the 'level' value
                    if (Regex.IsMatch(remainder, @"&level=\d+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
                    {
                        //check for 'level' in the 'result' value, and nix it
                        result = Regex.Replace(result, @"&level=\d+", "", RegexOptions.IgnoreCase);
                    }
                    //put it all together for the final rewrite string
                    result += remainder;
                    //last check for language - remove if the culture code already specified before the module (module can't override set language)
                    if (string.IsNullOrEmpty(cultureCode) == false)
                    {
                        string culturePattern = "&language=[a-z][a-z]-[A-Z][A-Z]";
                        result = Regex.Replace(result, culturePattern, "");
                    }
                }
            }
            return result;

        }

        /// <summary>
        /// Determines when to do a redirect.  This is separate to the rewriting process.  The module developer can create any type of Url redirect here, because the entire Url of the original request is passed in.
        /// </summary>
        /// <param name="tabId">Identified TabId, if known.  -1 if no valid tabid identified.</param>
        /// <param name="portalid">Identified portalId.</param>
        /// <param name="httpAlias">Identified httpAlias of the request.</param>
        /// <param name="requestUri">The original requested Url</param>
        /// <param name="queryStringCol">The querystring collection of the original request</param>
        /// <param name="options">The friendly url options that currently apply.</param>
        /// <param name="redirectLocation">Out parameter that shows where to redirect to.</param>
        /// <param name="messages">List of messages for debug purposes.  Add to this list to help debug your module.</param>
        /// <returns>true if 301 redirect is required, false if not.  If true, the redirectLocation value must be a valid fully qualified Url.</returns>

        public override bool CheckForRedirect(int tabId, int portalid, string httpAlias, Uri requestUri, System.Collections.Specialized.NameValueCollection queryStringCol, iFinity.DNN.Modules.UrlMaster.FriendlyUrlOptions options, out string redirectLocation, ref List<string> messages)
        {
            bool doRedirect = false;
            redirectLocation = "";//set blank location
            //compare to known pattern of old Urls
            //could be in old /itemid/xx format - if so, we want to redirect it
            //look for a product/category match, but only if it's not for the admin pages (ie, ctl/manview/mid/xxx)
            Regex pathRegex = new Regex(@"(?<!/ctl/[^/]+/mid/\d+)/(?<itemtype>ProductId|Category(?<n>\d+)?Id)/(?<id>\d+)(/Level/(?<lvl>\d+|a))?", RegexOptions.IgnoreCase);
            string path = requestUri.AbsoluteUri;
            string friendlyUrl = null;
            string msg = null;
            bool redirectMatch = false;
            bool buildPath = false;
            if (_noDnnPagePathTabId == tabId) buildPath = true;
            bool odd = true;
            MatchCollection pathMatches = pathRegex.Matches(path);
            foreach (Match pathMatch in pathMatches)
            {
                if (pathMatch.Success)
                {
                    CatalookUrlOptions clOptions = null;
                    string id = pathMatch.Groups["id"].Value;
                    string itemType = pathMatch.Groups["itemtype"].Value;
                    string level = null;
                    string catLevel = null;
                    if (pathMatch.Groups["lvl"].Success)
                        level = pathMatch.Groups["lvl"].Value;
                    if (pathMatch.Groups["n"].Success)
                    {
                        catLevel = pathMatch.Groups["n"].Value;
                        //remove the n from CategoryId -> category2Id -> categoryId.  This is for the lookup
                        itemType = itemType.Replace(catLevel, "");
                    }
                    int itemId;
                    if (int.TryParse(id, out itemId))
                    {
                        //ok, valid item Id found
                        string item = null;
                        string key = null;
                        string value = null;
                        string prefix = null;
                        string suffix = null;
                        string furlKey = "[not set]";
                        string cultureCode = queryStringCol["language"];//get the culture code
                        switch (itemType.ToLower())
                        {
                            case "categoryid":
                                if (level == "a")
                                {
                                    item = "AdvCategory";
                                    prefix = "ac";
                                    msg = "Identified as Advanced Category Url";
                                    key = "CategoryID";
                                    value = id;
                                    if (cultureCode != null)
                                        suffix = "-" + cultureCode;
                                    else
                                        suffix = "";
                                }
                                else
                                {
                                    item = "Category";
                                    prefix = "c";
                                    msg = "Identified as Category Url";
                                    suffix = "";
                                    //reconstruct the category level
                                    int levelInt = 1;
                                    int.TryParse(catLevel, out levelInt);
                                    if (levelInt > 1)
                                        key = "Category" + catLevel + "ID";
                                    else
                                        key = "CategoryID";
                                    value = id;
                                }
                                break;
                            case "productid":
                                item = "Product";
                                prefix = "p";
                                msg = "Identified as product Url";
                                key = "ProductID";
                                value = id;
                                if (cultureCode != null)
                                    suffix = "-" + cultureCode;
                                else
                                    suffix = "";
                                break;
                            default:
                                key = itemType;
                                value = id;
                                break;

                        }
                        //get the valid Url for this item

                        //look up the friendly url index using the item key

                        /*if (friendlyUrlIndex != null)
                        {
                            string friendlyUrl = (string)friendlyUrlIndex[furlKey];
                            if (friendlyUrl != null)
                            {
                                //ok, we're going to replace this in the Url
                                friendlyUrl = base.EnsureLeadingChar("/", friendlyUrl);
                                string result = pathRegex.Replace(path, friendlyUrl);
                                doRedirect = true;
                                redirectLocation = result;
                            }
                        }*/
                        if (buildPath)
                        {

                            Hashtable friendlyUrlIndex = null;
                            bool addFriendlyUrlSegment = false;
                            if (prefix != null)
                            {
                                furlKey = prefix + id + suffix;
                                //now lookup the friendly url index
                                clOptions = GetCatalookOptions(portalid);
                                friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(portalid, this, options, clOptions);

                                if (friendlyUrlIndex != null && friendlyUrlIndex.ContainsKey(furlKey))
                                {
                                    //look up the index for the item if we don't already have a friendly Url
                                    if (friendlyUrl == null) friendlyUrl = "";
                                    friendlyUrl += this.EnsureLeadingChar("/", (string)friendlyUrlIndex[furlKey]);
                                    addFriendlyUrlSegment = true;
                                    redirectMatch = true;
                                }
                            }
                            if (!addFriendlyUrlSegment)
                            {
                                //ok, there was a match on the Url, but no match.  Still include that segment, though
                                if (friendlyUrl == null) friendlyUrl = "";
                                friendlyUrl += this.EnsureLeadingChar("/", pathMatch.Value);
                            }
                        }
                        else
                        {
                            //put together an 'original' friendly url path, which then gets passed back into this provider
                            //to get what the Url should have looked like
                            if (friendlyUrl == null) friendlyUrl = "";
                            friendlyUrl += "/" + key + "/" + value;
                            if (level != null)
                                friendlyUrl += "/Level/" + level;
                            redirectMatch = true;
                            
                        }
                        //switch between odds and evens
                        if (odd) odd = false; else odd= true;
                    }
                }
            }
            //we have a friendly Url constructed, and at least part of it was replaced by a Friendly Url Lookup
            if (string.IsNullOrEmpty(friendlyUrl) == false && redirectMatch == true)
            {
                //now merge with the friendly url for the selected page
                string baseUrl = "";
                if (buildPath)
                {
                    string extension = options.PageExtension;
                    baseUrl = requestUri.Scheme + Uri.SchemeDelimiter + httpAlias + this.EnsureLeadingChar("/", friendlyUrl) + options.PageExtension;//put onto http Alias with no page path
                }
                else
                {
                    baseUrl = UrlController.BuildFullyQualifiedFriendlyUrl(httpAlias, tabId, friendlyUrl, this);
                }
                if (baseUrl != null)
                {
                    redirectLocation = baseUrl;
                    doRedirect = true;
                    msg += ", found friendly url " + friendlyUrl + ", redirecting";
                    messages.Add(msg);
                }
            }
                    /*else
                    {
                        msg += ", no friendly url found (key: " + furlKey + ", no redirect";
                        messages.Add(msg);
                    }*/
                
            
            return doRedirect;
        }

        public override string FriendlyName
        {
            get { return "iFinity Catalook Friendly Url Provider"; }
        }
        /// <summary>
        /// Returns any custom settings that are specific per-portal for this instance
        /// </summary>
        /// <remarks>
        /// This is used to write the values back out to the web.config from the Url Master UI
        /// </remarks>
        /// <returns>A Dictionary of the key/value pairs of the settings used in this provider.  Return empty dictionary if no portal specific options.</returns>
        public override Dictionary<string, string> GetProviderPortalSettings()
        {
            //returns the settings specific to this portal instance
            Dictionary<string, string> settings = new Dictionary<string, string>();
            //note : returning null for a setting removes it from the web.config on save
            if (_urlPath != null && _urlPath != "")
                settings.Add("urlPath", _urlPath);
            else
                settings.Add("urlPath", null);

            if (_noDnnPagePathTabId > 0)
                settings.Add("noDnnPagePathTabId", _noDnnPagePathTabId.ToString());
            else
                settings.Add("noDnnPagePathTabId", null);
            if (_startingProductId > 0)
                settings.Add("startingProductId", _startingProductId.ToString());
            else
                settings.Add("startingProductId", null);

            settings.Add("productUrlSource", _productUrlSource);
            settings.Add("categoryUrlSource", _categoryUrlSource);
            settings.Add("advCatUrlSource", _advCatUrlSource);
            settings.Add("hideListParm", _hideListParm.ToString());
            settings.Add("listParmToHide", _listParmToHide.ToString());
            settings.Add("hideCategoryForProducts", _hideCategoryForProducts.ToString());
            if (_productIdInUrl == true)
                settings.Add("productIdInUrl", _productIdInUrl.ToString());
            else
                settings.Add("productIdInUrl", null);
            return settings;
        }

        public override bool AlwaysUsesDnnPagePath(int portalId)
        {
            if (_noDnnPagePathTabId > 0)
            {
                return false;//if there is an exception, then return false
            }
            else
                return true;//no specific exceptions, return true every time
        }
        public override bool AlwaysCallForRewrite(int portalId)
        {
            return true;
        }
        public override bool IsLicensed(Uri requestUri, out string messageHtml, out string debugMessage)
        {
            //If this module had a licensing requirement, it would be checked here.  If not licensed
            //return false, and return some Html to be included on any pages where the provider is used, ie:
            //messageHtml = "<div>Hey! this isn't licensed!</div>"; debugMessage = "Fake licence message";
            bool isOk = true;
            debugMessage = null;
            messageHtml = null;
            return isOk;
        }
        /// <summary>
        /// Returns the Path of the Settings .ascx control (if supplied).  The Settings control is loaded by the Portal Urls page for the provider,
        /// and allows interaction of the settings by the end user, at a per-portal level.
        /// </summary>
        /// <remarks>
        /// Return empty or null string if no control exists.  The path should match the path specified in the DNN install manifest, and 
        /// point to an actual file.
        /// </remarks>
        public override string SettingsControlSrc
        {
            get { return "DesktopModules/iFinity.CatalookUrlProvider/Settings.ascx"; }
        }
        

#endregion
        #region internal methods and properties
        internal new string CleanNameForUrl(string title, FriendlyUrlOptions options)
        {
            bool replaced = false;
            //the base module Url PRovider contains a Url cleaning routine, which will remove illegal and unwanted characters from a string, using the specific friendly url options
            string cleaned = base.CleanNameForUrl(title, options, out replaced);
            string two = options.PunctuationReplacement + options.PunctuationReplacement;
            if (two.Length > 0)
            {
                cleaned = cleaned.Replace(two, options.PunctuationReplacement);
                cleaned = cleaned.Replace(two, options.PunctuationReplacement);
            }
            return cleaned;
            
        }
        #endregion

        internal int NoDnnPagePathTabId {
            get { return _noDnnPagePathTabId; }
            set { _noDnnPagePathTabId = value; }
        }

        internal string UrlPath {
            get { return _urlPath; }
            set { _urlPath = value; }
        }
        internal int StartingProductId
        {
            get { return _startingProductId;}
            set { _startingProductId = value; }
        }
        internal string ProductUrlSource
        {
            get { return _productUrlSource; }
            set { _productUrlSource = value; }
        }
        internal string CategoryUrlSource
        {
            get { return _categoryUrlSource; }
            set { _categoryUrlSource = value; }
        }
        internal string AdvCatUrlSource
        {
            get { return _advCatUrlSource; }
            set { _advCatUrlSource = value; }
        }
        internal int ListParmToHide
        {
            get { return _listParmToHide; }
            set { _listParmToHide = value; }
        }
        internal bool HideListParm
        {
            get { return _hideListParm; }
            set { _hideListParm = value; }
        }
        internal bool HideCategoryForProducts
        {
            get { return _hideCategoryForProducts; }
            set { _hideCategoryForProducts = value; }
        }
        internal bool ProductIdInUrl
        {
            get { return _productIdInUrl; }
            set { _productIdInUrl = value; }
        }

    }
}
